package com.pan.download5.ctrl;

import com.pan.download5.config.StoryWriterConfig;
import com.pan.download5.sync.CasUnlockAndBlock;
import com.pan.download5.sync.UnlockAndBlock;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ChapterSyncWriterImpl implements ChapterSyncWriter{

    //将控制写入顺序的和控制获取写入资源的分离
    private volatile int completeId;
    private String storyName;

    public ChapterSyncWriterImpl(String storyName) {
        this.completeId = -1;
        this.storyName = storyName;
        start();
    }

    private ChapterWriter chapterWriter;

    public void start() {

        chapterWriter = new ChapterWriter(storyName);
    }

    public void close() {
        chapterWriter.close();
    }


    private void write(String content) {
        chapterWriter.write(content);
    }

    /**
     * 这个方法已经被废弃
     * 详情请见方法 writeChapter() 处的注释
     *
     *
     * 之所以这样写，每个网站每本小说的标题格式都不一样，顶着两个或多个标题实在是太恶心了
     * 比如章节名是这样的格式   ”1145. 第一一四五章 第1145章 野兽、第1145章 野兽“
     *
     * 另外，某些TXT阅读软件的章节分割可能无法识别诸如“114.野兽” 、“一一四五、先辈” 这样的标题，
     * 所以也有必要清洗标题，然后统一为 “第114章 野兽” 这样的格式
     *
     * 但这个正则表式，在某情况下如果洗的次数多了，是会有问题的。 比如标题 “114. 一一只一只野兽”
     * 清洗第一次后会变成“一一只一只野兽”这是正确的，但如果再次再次清洗后就会变成 “只一只野兽”，破坏了原有标题
     *
     * 但如果不多次清洗则会面临开头所举的例子那样 ”1145. 第一一四五章 第1145章 野兽、第1145章 野兽“洗不干净
     * 但这也是没办法的事情，格式太多太乱，我能力有限没办法完全兼顾每一种格式，所以我这里只选择清洗一次，
     * 如果需要，请自行修改正则匹配式 或者更改清洗方法。
     *
     * Author: Pān Qí-ān
     * 作者：潘祺安
     * @param title
     * @param id
     * @return
     */
    //这个正则表达式不是万能的，只是尽可能匹配大多数情况下的章节序号，有时候会过匹配
    private final String clearChapterIdRegex = "^\\s*第?\\s*(?:[一二两三四五六七八九十百千万0〇零]+|\\d+)\\s*[\\.、]?\\s*[章节回]?、?";
    private final String clearRepeatTitleRegex = "(.+)[\\s,\\./\\\\，。、]*?\\1$";
    @Deprecated
    private String clearTitle(String title, int id) {
        Matcher repeatMatcher = Pattern.compile(clearRepeatTitleRegex).matcher(title);
        //1.8就是坑爹，就一简单的替换匹配内容都要创建个sb，写个lambda就行了
        StringBuffer sb = new StringBuffer();
        //必要时可以改成循环
        if (repeatMatcher.find()) {
            String group = repeatMatcher.group(1);
            repeatMatcher.appendReplacement(sb,group);//loop end
            repeatMatcher.appendTail(sb);
            title = sb.toString();
        }
//必要时可以改成循环
//        int len;
//        do {
//            len = title.length();
//            title = title.replaceFirst(clearChapterIdRegex, "");
//        }while (title.length()!=len);
        title = title.replaceFirst(clearChapterIdRegex,"");
        String result = "第" + id + "章 " + title.trim();
        return result;
    }

    //经过测试自旋11451419198次大概要3-4秒？可能要看系统性能，和当时系统的负载情况
    //自旋11451419198次疑似有点太...多，还是改成自旋1145141919次吧
    private final int SPIN_LIMIT = 1145141919;
    private UnlockAndBlock lock = new CasUnlockAndBlock();//new LockSemaphoreUnlockAndBlock();//new SemaphoreUnlockAndBlock();


    @Deprecated
    private void printMessage(String message){
        System.out.println(message);
    }
    //防止接下来的任务，线程阻塞了，因此应该试着唤醒下一个任务的线程
    private void writeCompleteIdAndWakeUpNextThread(int completeId) {
        lock.lock();
        try {
            //拿到锁后，修改已经完成的章节信息
            this.completeId = completeId;
            //并且尝试唤醒后续章节
            lock.waitBlockThread(completeId+1);
        } finally {//最后释放锁
            lock.unlock();
        }
    }

    private void parkIfPreviousTaskUncompleted(int chapterId) {
        lock.lock();
        //拿到锁后，再次判断当前完成的章节，如果恰好轮到自己就释放锁并且放行不再等待
        if (completeId == (chapterId - 1)) {
            try {
                return;
            }finally {
                lock.unlock();
            }
        }
        //否则就释放锁并且阻塞线程，等待前一个章节完成后唤醒该线程
        lock.unlockAndBlock(chapterId);
        // 已解决
        // 通过自定义锁，详情见UnlockAndBlock来保证释放锁和阻塞这两个操作的能是一个原子操作
        //
        //问题：
        //释放锁后的这个时间窗口打破了原子性怎么办？
        //有没有一个能保证原子性的，释放锁后同时将该线程阻塞起来的操作？
        //解决方法：
        // 我能力有限，无法实现出同时释放锁后同时将该线程阻塞还能保证原子性，
        // （或许能试一下多个线程互相操作？感觉难度比较高，而且不确定是否可行）
        //尝试 1： 就先拿着锁，阻塞线程，如果另外的线程要想拿该锁，就先唤醒该线程
        // 不太行，等我研究一下

    }

    private void writeWait(int chapterId) {
        int previousId = chapterId - 1;
        for (;;) {
            for (int i = 0; i < SPIN_LIMIT; i++)
                if (previousId == completeId)
                    return;//线程结束自旋，放行
            this.parkIfPreviousTaskUncompleted(chapterId);//自旋太久就先挂起了
            //被唤醒后，应该再次判断是否符合条件，即前面的章节完成写入完成后，才能轮到自己写入。
        }
    }


    @Override
    public void writeChapter(int chapterId, String content) {
        writeWait(chapterId);
//        String title = clearTitle(chapterTitle, chapterId+1);//title的章节从0开始
//        String content = title + "\n\n\n\n" + chapterContent + "\n\n\n\n\n";
        write(content);
        //虽然不一定写入到文件里面去，但已经一定已经写入到缓冲区里面了，缓冲区的数据是遵循写入顺序的
        this.writeCompleteIdAndWakeUpNextThread(chapterId);//当前章节写入完成
    }
    /**
     * 废弃原因：
     * 这里无论是从多线程来说，还是从设计模式上来说，这里设计的都是不好的
     * 首先从多线程来说，标题清洗和最终写入内容的拼接其实并不是多线程所共享的状态,
     * 这样的代码不应该放在被同步代码块里面的，这将额外增加开销
     * String title = clearTitle(chapterTitle, chapterId+1);//title的章节从0开始
     * String content = title + "\n\n\n\n" + chapterContent + "\n\n\n\n\n";
     * 当然一个简单的补救修改措施是讲这两行代码移出同步的代码块。
     * 这虽然解决了多线程的问题，但是从整个程序设计的角度上来说这样写仍然是不好的，
     * 应明确，这是一个专门负责同步章节写入文件的类，这个类应专注于搞同步章节写入文件，
     * 而不应在这里写这些内容处理的逻辑，这违反了面向对象原则，违反了单一职责原则。
     * 因此这里的代码其实从接口设计上就错了，需要重写设计接口
     * 同步只需要一个参数 int chapterId,只需要一个写入内容参数 String content
     *
     * 清洗标题和拼接最终写入文件内容的逻辑，应该转移到TaskHandle里去
     *
     */
    //核心方法
    @Deprecated
    public void writeChapter(int chapterId, String chapterTitle, String chapterContent) {

        String title = clearTitle(chapterTitle, chapterId+1);//title的章节从0开始
        String content = title + "\n\n\n\n" + chapterContent + "\n\n\n\n\n";
        //清完标题和拼接好内容就 先等待
        writeWait(chapterId);
//        String title = clearTitle(chapterTitle, chapterId+1);//title的章节从0开始
//        String content = title + "\n\n\n\n" + chapterContent + "\n\n\n\n\n";
        write(content);
        //虽然不一定写入到文件里面去，但已经一定已经写入到缓冲区里面了，缓冲区的数据是遵循写入顺序的
        this.writeCompleteIdAndWakeUpNextThread(chapterId);//当前章节写入完成
//        printMessage("章节写入完成："+title);
    }


    class ChapterWriter implements StoryWriterConfig {
        //这些变量以后或许转移到配置文件里面更好
        private final String outputDir = this.getOutputDir();//"story/";
        private final boolean isAppend = this.getWriteAppend();//false;
        private FileWriter fileWriter;

        public ChapterWriter(String name) {
            File file = new File(outputDir + name + ".txt");
            try {
                if (!file.exists()) {
                    file.createNewFile();
                }
                fileWriter = new FileWriter(file, isAppend);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        //感觉这个不写也没啥问题
        private AtomicInteger permit = new AtomicInteger(1);
        public void write(String content) {
            while (!permit.compareAndSet(1,0));
            try {
                fileWriter.write(content);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            while (!permit.compareAndSet(0,1));
        }

        public void close() {
            while (!permit.compareAndSet(1,0));
            try {
                fileWriter.flush();
                fileWriter.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            lock.close();
            while (!permit.compareAndSet(0,1));
        }
    }
}
